/*
This file implements SanRazor for improving efficiency
*/
#ifdef ENABLE_MAPLE_SAN

#include "asan_razor.h"
#include "asan_interfaces.h"
#include "asan_module.h"
#include "asan_ud.h"
#include <set>
#include <atomic>
#include <filesystem>

namespace maple {

std::string SanRazorCounterName;
std::string SanRazorSizeName;
std::string SanRazorIsInitializedName;
std::string SanRazorFilenameName;
std::atomic<size_t> SanRazorSize;
std::map<size_t, AsanRazorCheckID> SanRazorCheckIDMap;
std::map<AsanRazorCheckID, size_t> SanRazorCallSiteIdMap;
std::map<const ASanRazorStmtKey, AsanRazorCheckID> SanRazorStmtKeyCheckIDMap;
std::map<const ASanRazorStmtKey, AsanBlockCost> SanRazorStmtKey2CostMap;
std::set<ASanRazorStmtKey> SanRazorAsanRelevantStmtKey;
std::string IDMAP_SUFFIX = ".idmap";
std::string SanRazorCheckIDMapFileName;
std::set<AsanRazorCheckID> AsanRelevanCheckIDs;
bool SanRazorIs2ndCompile = false;

std::string SanRazorConcatPrefixAndFilename(std::filesystem::path prefix, std::string filename) {
  std::string _name = filename;
  while (_name[0] == '/') {
    _name = _name.substr(1);
  }
  std::filesystem::path full_filename = prefix / _name;
  return full_filename.string();
}

std::string GetSanrazorCounterName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_cov_counter_" + postfix;
}

std::string GetSanrazorSizeName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_cov_size_" + postfix;
}

std::string GetSanrazorIsInitializedName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_cov_is_initialized_" + postfix;
}

std::string GetSanrazorFilenameName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_filename_" + postfix;
}

std::string GetSanrazorAppendInfoCallerName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_append_info_" + postfix;
}

std::string GetSanrazorAppendInfoStructVarName(ModuleAddressSanitizer& AsanModule) {
  std::string postfix = AsanModule.GetModuleSymbolPostfix();
  return "__sanrazor_append_info_var_" + postfix;
}

std::string GetSanrazorAppendInfoCallerDefFilePath(ModuleAddressSanitizer& AsanModule) {
  std::string src_path = AsanModule.GetSrcFilePath();
  return src_path + "__sanrazor_append_info.c";
}

void AddGlobalSanrazorCounter(ModuleAddressSanitizer& AsanModule, size_t size, bool isInit) {
  std::string name = GetSanrazorCounterName(AsanModule);
  AsanModule.AddGlobalUIntArray(name, std::vector<uint64_t>(size, 0), isInit);
}

void AddGlobalSanrazorSize(ModuleAddressSanitizer& AsanModule, size_t size, bool isInit) {
  std::string name = GetSanrazorSizeName(AsanModule);
  AsanModule.AddGlobalUInt(name, size, isInit);
}

void AddGlobalSanrazorIsInitialized(ModuleAddressSanitizer& AsanModule, bool isInit) {
  std::string name = GetSanrazorIsInitializedName(AsanModule);
  AsanModule.AddGlobalChar(name, 0, isInit);
}

void AddGlobalSanrazorFilename(ModuleAddressSanitizer& AsanModule, bool isInit) {
  std::string name = GetSanrazorFilenameName(AsanModule);
  AsanModule.AddGlobalString(name, AsanModule.GetSrcFilePath(), isInit);
}

void InitializeSanRazorGlobalNames(ModuleAddressSanitizer& AsanModule) {
  SanRazorCounterName = GetSanrazorCounterName(AsanModule);
  SanRazorSizeName = GetSanrazorSizeName(AsanModule);
  SanRazorIsInitializedName = GetSanrazorIsInitializedName(AsanModule);
  SanRazorFilenameName = GetSanrazorFilenameName(AsanModule);
  SanRazorSize = 0;
  std::filesystem::path prefix_dir = GetSanRazorCheckIDMapDirPath();
  SanRazorCheckIDMapFileName = AsanModule.GetSrcFilePath() + IDMAP_SUFFIX;
  SanRazorCheckIDMapFileName = SanRazorConcatPrefixAndFilename(prefix_dir, SanRazorCheckIDMapFileName);
  SanRazorIs2ndCompile = ((MeOption::asanFlags & 0x04) > 0);
  // besides var names, we also create global symbols
  AddGlobalSanrazorCounter(AsanModule, 1, true);
  AddGlobalSanrazorSize(AsanModule, 1, true);
  AddGlobalSanrazorFilename(AsanModule, true);
  AddGlobalSanrazorIsInitialized(AsanModule, true);
}

void SetSanRazorGlobals(ModuleAddressSanitizer& AsanModule) {
  AddGlobalSanrazorCounter(AsanModule, SanRazorSize, false);
  AddGlobalSanrazorSize(AsanModule, SanRazorSize, false);
  AddGlobalSanrazorFilename(AsanModule, false);
  AddGlobalSanrazorIsInitialized(AsanModule, false);
}

void AddASanRazorSignalHandlerRegister(ModuleAddressSanitizer& AsanModule) {
  MIRBuilder* builder = AsanModule.GetMIRModule()->GetMIRBuilder();
  MIRFunction *registerAsanCovFunction =
      AsanModule.GetOrInsertFunction(kAsanCovRegisterName, GlobalTables::GetTypeTable().GetVoid(), {});
  MapleVector<BaseNode *> args(builder->GetCurrentFuncCodeMpAllocator()->Adapter());
  CallNode *call__asan_init = builder->CreateStmtCall(registerAsanCovFunction->GetPuidx(), args);
  AsanModule.AppendStmtToGlobalInit(call__asan_init);
}

void AddSanRazorAppendInfo(ModuleAddressSanitizer& AsanModule) {
  MIRBuilder* moduleBuilder = AsanModule.GetMIRModule()->GetMIRBuilder();
  std::string structName = GetSanrazorAppendInfoCallerName(AsanModule);
  MIRFunction* caller = moduleBuilder->GetOrCreateFunction(structName, TyIdx(PTY_void));
  caller->AllocPregTab();
  caller->AllocSymTab();
  caller->AllocLabelTab();
  caller->SetWithSrc(false);
  caller->SetAttr(FuncAttrKind::FUNCATTR_section);
  caller->SetAttr(FuncAttrKind::FUNCATTR_public);
  caller->SetAttr(FuncAttrKind::FUNCATTR_extern);
  caller->GetFuncAttrs().SetPrefixSectionName(".init_array");
  AsanModule.GetMIRModule()->AddFunction(caller);

  // The following code add StmtNode to the caller
  // We need to change current function to the newly created caller first
  // otherwise, there are errors in following phases
  MIRBuilder* builder = caller->GetCodeMempool()->New<MIRBuilder>(caller->GetModule());
  auto old_func = builder->GetCurrentFunction();
  builder->SetCurrentFunction(*caller);
  MIRType *voidType = GlobalTables::GetTypeTable().GetVoid();
  // void __san_cov_append_info(uint64_t *array, char *is_initialized, char* filename, size_t size)
  MIRFunction *__san_cov_append_info = getOrInsertFunction(builder, kSanRazorCovAppendInfo, voidType, {});
  MapleVector<BaseNode *> argcov(AsanModule.GetMIRModule()->GetMPAllocator().Adapter());
  GStrIdx arrayvarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorCounterName);
  MIRSymbol* arrayvar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(arrayvarIdx);
  BaseNode *arraynode = builder->CreateExprAddrof(0, *arrayvar);
  argcov.emplace_back(arraynode);
  GStrIdx isinitvarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorIsInitializedName);
  MIRSymbol* isinitvar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(isinitvarIdx);
  BaseNode *isinitnode = builder->CreateExprAddrof(0 ,*isinitvar);
  argcov.emplace_back(isinitnode);
  GStrIdx filenamevarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorFilenameName);
  MIRSymbol* filenamevar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(filenamevarIdx);
  BaseNode *filenamenode = builder->CreateExprDread(*filenamevar);
  argcov.emplace_back(filenamenode);
  GStrIdx sizevarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorSizeName);
  MIRSymbol* sizevar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(sizevarIdx);
  BaseNode *sizenode = builder->CreateExprDread(*sizevar);
  argcov.emplace_back(sizenode);
  // CallNode *callcov = builder->CreateStmtCall(__san_cov_append_info->GetPuidx(), argcov);
  CallNode* callcov = AsanModule.GetMIRModule()->GetMemPool()->New<CallNode>(caller->GetCodeMemPoolAllocator(),
      OP_call, __san_cov_append_info->GetPuidx());
  callcov->SetNOpnd(argcov);
  callcov->SetNumOpnds(argcov.size());
  BlockNode* block = caller->GetCodeMemPool()->New<BlockNode>();
  block->AddStatement(callcov);
  caller->SetBody(block);
  // reset back the function
  builder->SetCurrentFunction(*old_func);
}

void DumpSanRazorCheckIDMap(std::string filename) {
  std::filesystem::path full_filename = filename;
  if (ASAN_DEBUG_MODE) {
    LogInfo::MapleLogger() << "Dumping SanRazorCheckIDMap to " << full_filename.string() << "\n";
  }
  std::filesystem::create_directories(full_filename.parent_path());
  std::ofstream file;
  // Change cout's buffer to file.
  std::streambuf *backup = LogInfo::MapleLogger().rdbuf();
  LogInfo::MapleLogger().rdbuf(file.rdbuf());
  file.open(full_filename.string(), std::ios::out);
  file << std::hex;
  for (auto &pair : SanRazorCheckIDMap) {
    bool isAsanRelevant = (AsanRelevanCheckIDs.find(pair.second) != AsanRelevanCheckIDs.end());
    if (isAsanRelevant)
      file << pair.first << " " << pair.second << " 1\n";
    else
      file << pair.first << " " << pair.second << " 0\n";
  }
  // Restore cout's buffer.
  LogInfo::MapleLogger().rdbuf(backup);
  file.close();
}

void LoadSanRazorCheckIDMap(std::string filename) {
  std::ifstream file;
  file.open(filename);
  std::string line;
  while (std::getline(file, line)) {
    std::istringstream iss(line);
    size_t callSiteId;
    AsanRazorCheckID id;
    // int isAsanRelevant;
    iss >> std::hex >> callSiteId >> id;
    SanRazorCheckIDMap[callSiteId] = id;
    SanRazorCallSiteIdMap[id] = callSiteId;
  }
  file.close();
}

void LoadSanRazorStmtKeyCheckIDMap(std::string filename) {
  CHECK_FATAL((ASAN_RAZOR_IGNORE_IDMAP_FILE || std::filesystem::exists(filename)), \
      "%s does not exist\nExit.", filename.c_str());
  std::ifstream file;
  file.open(filename, std::ios::in);
  std::string src_path = AsanModulePtr->GetSrcFilePath();
  std::string line;
  while (std::getline(file, line)) {
    std::istringstream iss(line);
    size_t callSiteId;
    AsanRazorCheckID id;
    int isAsanRelevant;
    iss >> std::hex >> callSiteId >> id >> isAsanRelevant;
    ASanRazorStmtKey key(callSiteId, src_path);
    SanRazorStmtKeyCheckIDMap[key] = id;
    // TODO: to decide cost
    SanRazorStmtKey2CostMap[key] = 1.0;
    if (isAsanRelevant > 0)
      SanRazorAsanRelevantStmtKey.insert(key);
  }
  file.close();
}

void SanRazorClearEverything() {
  SanRazorCounterName = "";
  SanRazorSizeName = "";
  SanRazorIsInitializedName = "";
  SanRazorFilenameName = "";
  SanRazorSize = 0;
  SanRazorCheckIDMap.clear();
  SanRazorStmtKeyCheckIDMap.clear();
  SanRazorCheckIDMapFileName = "";
  AsanRelevanCheckIDs.clear();
}

std::string SanRazorLineDelimiter = ASAN_RAZOR_DEFAULT_DELIMITER;
std::map<const ASanRazorStmtKey, AsanRazorCheckCount> SanRazorStmtCountMap;

void LoadSanRazorLog() {
  char* file_path = std::getenv(ASAN_RAZOR_LOG_MACRO);
  std::string filename;
  if (file_path == nullptr) {
    filename = ASAN_RAZOR_DEFAULT_LOG_FILE;
  } else {
    filename = std::string(file_path);
  }
  std::ifstream file;
  file.open(filename);
  if (!std::filesystem::exists(filename)) {
    LogInfo::MapleLogger() << filename << " does not exist\nExit.";
    abort();
  }
  std::string line;
  while (std::getline(file, line)) {
    std::string src_path = line.substr(0, line.find(SanRazorLineDelimiter));
    if (src_path.size() < 1) continue;
    line = line.substr(src_path.size() + SanRazorLineDelimiter.size());
    std::string id_str = line.substr(0, line.find(SanRazorLineDelimiter));
    size_t id = std::stoull(id_str, nullptr, 16);
    std::string count_str = line.substr(id_str.size() + SanRazorLineDelimiter.size());
    AsanRazorCheckCount count = std::stoull(count_str, nullptr, 16);
    // Save map of (id, src_path) => count
    // Then we sort the map by count, ASAP will remove K most frequent checks
    ASanRazorStmtKey key(id, src_path);
    if (SanRazorStmtCountMap.find(key) == SanRazorStmtCountMap.end()) {
      SanRazorStmtCountMap[key] = count;
    } else {
      SanRazorStmtCountMap[key] += count;
    }
  }
  file.close();
}

std::string GetSanRazorCheckIDMapDirPath() {
  char* idmap_dir = std::getenv(ASAN_RAZOR_IDMAP_DIR_MACRO);
  std::string prefix_dir;
  if (idmap_dir == nullptr) {
    prefix_dir = ASAN_RAZOR_DEFAULT_IDMAP_DIR;
  } else {
    prefix_dir = std::string(idmap_dir);
  }
  return prefix_dir;
}

void LoadAllSanRazorStmtKeyCheckIDMaps() {
  std::filesystem::path prefix_dir = GetSanRazorCheckIDMapDirPath();
  std::set<std::string> loaded_files;
  for (auto &pair : SanRazorStmtCountMap) {
    std::string filename = pair.first.src_path;
    if (loaded_files.find(filename) == loaded_files.end()) {
      loaded_files.insert(filename);
      std::string map_filename = SanRazorConcatPrefixAndFilename(prefix_dir, filename + IDMAP_SUFFIX);
      LoadSanRazorStmtKeyCheckIDMap(map_filename);
    }
  }
}

ASanRazor::ASanRazor() {}

bool ASanRazor::InstrumentFunctionForProfiling(MeFunction &F, const AsanVirtualBlockList &AsanBlocks) {
  this->PreprocessFunctionForProfiling(F, AsanBlocks);
  // Perform UD analysis here, such we can spot redundant UC as well 
  this->udchain.initME(F, this->asanStmtIDSet);
  InstrumentBlockForProfiling(this->mirfunc->GetBody());
  return true;
}

bool ASanRazor::RemoveRedundantInstrumentation(MeFunction &F, const AsanVirtualBlockList &AsanBlocks) {
  //this->PreprocessFunctionForProfiling(F, AsanBlocks);
  std::set<StmtNode*> toRemoveStmtNodeSet;
  std::set<StmtID> SanRazorToRemoveStmtIDs = GetRemoveAsanID();
  if (ASAN_DEBUG_MODE){
    LogInfo::MapleLogger() << "Check to be removed "<<SanRazorToRemoveStmtIDs.size()<<" \n";
  }
  size_t num_asan_blocks = 0;
  for (auto &asanBlock : *AsanBlockListPtr->blocks) {
    StmtID first_id = asanBlock.first->GetStmtID();
    if (SanRazorToRemoveStmtIDs.find(first_id) != SanRazorToRemoveStmtIDs.end()) {
      num_asan_blocks++;
      StmtNode* cur = asanBlock.first;
      while (cur != nullptr && cur != asanBlock.last) {
        toRemoveStmtNodeSet.insert(cur);
        cur = cur->GetNext();
      }
      toRemoveStmtNodeSet.insert(asanBlock.last);
    }
  }
  if (ASAN_DEBUG_MODE) {
    LogInfo::MapleLogger() << "SanRazor removes " << num_asan_blocks << " asan blocks " << \
      toRemoveStmtNodeSet.size() << " StmtNodes\n";
  }
  
  std::vector<StmtBlockNodePair> stmtBlockList = getAllOrderedStmtNodeList(F.GetMirFunc()->GetBody());
  for (auto &pair : stmtBlockList) {
    if (toRemoveStmtNodeSet.find(pair.first) != toRemoveStmtNodeSet.end()) {
      pair.second->RemoveStmt(pair.first);
    }
  }

  return true;
}

void ASanRazor::ClearSanRazorCoverageStmtNodes() {
  for (StmtBlockNodePair &pair : asanCovStmts) {
    pair.second->RemoveStmt(pair.first);
  }
}

void ASanRazor::_GetLabelIdx2LabelNodeFromBlock(BlockNode* block) {
  for (auto &stmt : block->GetStmtNodes()) {
    if (stmt.GetOpCode() == OP_label) {
      LabelNode *label = dynamic_cast<LabelNode *>(&stmt);
      CHECK_NULL_FATAL(label);
      this->labelIdx2LabelNode[label->GetLabelIdx()] = label;
      this->labelIdx2BlockNode[label->GetLabelIdx()] = block;
    } else {
      for (size_t i = 0; i < stmt.NumOpnds(); ++i) {
        if (stmt.Opnd(i)->GetOpCode() == OP_block) {
          BlockNode *tmpblock = dynamic_cast<BlockNode *>(stmt.Opnd(i));
          CHECK_NULL_FATAL(tmpblock);
          _GetLabelIdx2LabelNodeFromBlock(tmpblock);
        }
      }
    }
  }
}

std::map<LabelIdx, LabelNode*> ASanRazor::GetLabelIdx2LabelNode() {
  if (!this->labelIdx2LabelNode.empty()) {
    return this->labelIdx2LabelNode;
  }
  _GetLabelIdx2LabelNodeFromBlock(this->mirfunc->GetBody());
  return this->labelIdx2LabelNode;
}

std::map<LabelIdx, BlockNode*> ASanRazor::GetLabelIdx2BlockNode() {
  if (!this->labelIdx2BlockNode.empty()) {
    return this->labelIdx2BlockNode;
  }
  _GetLabelIdx2LabelNodeFromBlock(this->mirfunc->GetBody());
  return this->labelIdx2BlockNode;
}

void ASanRazor::_GetStmtID2node(BlockNode* block) {
  for (auto &stmt : block->GetStmtNodes()) {
    this->stmtID2node[stmt.GetStmtID()] = &stmt;
    this->stmtID2BlockNode[stmt.GetStmtID()] = block;    
    for (size_t i = 0; i < stmt.NumOpnds(); ++i) {
      if (stmt.Opnd(i)->GetOpCode() == OP_block) {
        BlockNode *tmpblock = dynamic_cast<BlockNode *>(stmt.Opnd(i));
        CHECK_NULL_FATAL(tmpblock);
        _GetStmtID2node(tmpblock);
      }
    }
  }
}

std::map<StmtID, StmtNode*> ASanRazor::GetStmtID2node() {
  if (!this->stmtID2node.empty()) {
    return this->stmtID2node;
  }
  _GetStmtID2node(this->mirfunc->GetBody());
  return stmtID2node;
}

AsanVirtualBlock* ASanRazor::GetAsanVirtualBlockWithIDBlock(const AsanVirtualIDBlock &idblock) {
  // check if the block in this function
  StmtNode* first = this->stmtID2node[idblock.first];
  if (first == nullptr) {
    return nullptr;
  }
  AsanVirtualBlock* block = this->module->GetMemPool()->New<AsanVirtualBlock>();
  block->first = first;
  block->last = this->stmtID2node[idblock.last];
  block->normal = this->stmtID2node[idblock.normal];
  if (block->normal == nullptr) {
    block->normal = block->last->GetNext();
  }
  for (auto crash_id : idblock.crashes) {
    block->crashes.push_back(this->stmtID2node[crash_id]);
  }
  return block;
}

std::set<StmtID> ASanRazor::GetAsanStmtIDSet(const AsanVirtualBlockList &AsanBlocks) {
  if (!asanStmtIDSet.empty()) {
    return asanStmtIDSet;
  }
  for (auto &idblock : *(AsanBlocks.idblocks)) {
    AsanVirtualBlock* block = GetAsanVirtualBlockWithIDBlock(idblock);
    if (block == nullptr) continue;
    StmtNode *cur = block->first;
    StmtNode *last = block->last;
    CHECK_NULL_FATAL(cur);
    CHECK_NULL_FATAL(last);
    while (cur != last) {
      asanStmtIDSet.insert(cur->GetStmtID());
      stmtID2asanBlock[cur->GetStmtID()] = block;
      cur = cur->GetNext();
    }
    asanStmtIDSet.insert(idblock.last);
    stmtID2asanBlock[idblock.last] = block;
  }
  return asanStmtIDSet;
}

CallNode *ASanRazor::GetCallAsanCovStmt(AsanRazorCheckID id) {
  // Store mapping first
  size_t callSiteId = SanRazorSize++;
  if (!SanRazorIs2ndCompile) {
    SanRazorCheckIDMap[callSiteId] = id;
  }
  else {
    // make sure the given sanrazor log file and the idmap file are consistent
    // SPEC2017 removes the build directory before rebuilding
    // resulting in all idmap files are deleted
    if (ASAN_RAZOR_IGNORE_IDMAP_FILE) {
      SanRazorCheckIDMap[callSiteId] = id;
      ASanRazorStmtKey key(callSiteId, this->srcpath);
      SanRazorStmtKeyCheckIDMap[key] = id;
    } else {
      CHECK_FATAL(SanRazorCheckIDMap[callSiteId] == id, "SanRazorCheckIDMap[%x] != %x", callSiteId, id);
    }
  }
  // Build call node
  MIRBuilder *builder = module->GetMIRBuilder();
  MIRType *voidType = GlobalTables::GetTypeTable().GetVoid();
  // void __san_cov_trace_pc(uint64_t *array, uint32_t stmt_id, char *is_initialized, char* filename, size_t size)
  if (ASAN_RAZOR_TRACE_FUNC == 1) {
    MIRFunction *__san_cov_trace_pc = getOrInsertFunction(builder, kSanRazorCov, voidType, {});
    MapleVector<BaseNode *> argcov(this->module->GetMPAllocator().Adapter());
    GStrIdx arrayvarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorCounterName);
    MIRSymbol* arrayvar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(arrayvarIdx);
    // BaseNode *arraynode = builder->CreateExprDread(*arrayvar);
    BaseNode *arraynode = builder->CreateExprAddrof(0, *arrayvar);
    argcov.emplace_back(arraynode);
    argcov.emplace_back(builder->GetConstUInt64(callSiteId));
    GStrIdx isinitvarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorIsInitializedName);
    MIRSymbol* isinitvar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(isinitvarIdx);
    BaseNode *isinitnode = builder->CreateExprAddrof(0 ,*isinitvar);
    argcov.emplace_back(isinitnode);
    GStrIdx filenamevarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorFilenameName);
    MIRSymbol* filenamevar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(filenamevarIdx);
    BaseNode *filenamenode = builder->CreateExprDread(*filenamevar);
    argcov.emplace_back(filenamenode);
    GStrIdx sizevarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorSizeName);
    MIRSymbol* sizevar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(sizevarIdx);
    BaseNode *sizenode = builder->CreateExprDread(*sizevar);
    argcov.emplace_back(sizenode);
    CallNode *callcov = builder->CreateStmtCall(__san_cov_trace_pc->GetPuidx(), argcov);
    return callcov;
  } else if (ASAN_RAZOR_TRACE_FUNC == 2) {
    MIRFunction *__san_cov_trace_pc = getOrInsertFunction(builder, kSanRazorCov2, voidType, {});
    MapleVector<BaseNode *> argcov(this->module->GetMPAllocator().Adapter());
    GStrIdx arrayvarIdx = GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(SanRazorCounterName);
    MIRSymbol* arrayvar = GlobalTables::GetGlobalTables().GetGsymTable().GetSymbolFromStrIdx(arrayvarIdx);
    BaseNode *arraynode = builder->CreateExprAddrof(0, *arrayvar);
    argcov.emplace_back(arraynode);
    argcov.emplace_back(builder->GetConstUInt64(callSiteId));
    CallNode *callcov = builder->CreateStmtCall(__san_cov_trace_pc->GetPuidx(), argcov);
    return callcov;
  } else {
    CHECK_FATAL(false, "Unknow type of trace function (%d)", ASAN_RAZOR_TRACE_FUNC);
  }
  
}

CallNode *ASanRazor::GetCallDumpAsanCovStmt() {
  MIRBuilder *builder = module->GetMIRBuilder();
  MIRType *voidType = GlobalTables::GetTypeTable().GetVoid();
  MIRFunction *__san_cov_trace_pc = getOrInsertFunction(builder, kSanRazorCovDump, voidType, {});
  MapleVector<BaseNode *> argcov(this->module->GetMPAllocator().Adapter());
  ConststrNode *conststr = this->module->GetMemPool()->New<ConststrNode>(this->srcpathidx);
  conststr->SetPrimType(PTY_a64);
  CallNode *callcov = builder->CreateStmtCall(__san_cov_trace_pc->GetPuidx(), argcov);
  return callcov;
}

void ASanRazor::InstrumentAsanCheckForProfiling(StmtNode *stmt, const AsanVirtualBlock &asanBlock, BlockNode *block) {
  CHECK_NULL_FATAL(stmt);
  CHECK_NULL_FATAL(asanBlock.first);
  CHECK_NULL_FATAL(asanBlock.last);
  CHECK_NULL_FATAL(asanBlock.normal);
  CHECK_FATAL(asanBlock.crashes.size() > 0, "crash branch should not be empty");
  // The first stmtid is the stmtid for identifying the asan check
  AsanRazorCheckID normal_id = GetTrueBranchID(stmt);
  AsanRazorCheckID crash_id = GetFalseBranchID(stmt);
  // Insert call __san_cov_trace_pc
  CallNode *normal_call = GetCallAsanCovStmt(normal_id);
  AsanRelevanCheckIDs.insert(normal_id);
  // We do not use the normal pointer since two asan blocks can be continuous
  // block->InsertBefore(asanBlock.normal, normal_call);
  block->InsertAfter(asanBlock.last, normal_call);
  asanCovStmts.push_back(StmtBlockNodePair(normal_call, block));
  if (!asanBlock.crashes.empty()) {
    AsanRelevanCheckIDs.insert(crash_id);
  }
  for (auto crash_branch : asanBlock.crashes) {
    // before asan report and crash
    // we dump the coverage info
    CallNode *crash_call = GetCallAsanCovStmt(crash_id);
    CallNode *dump_call = GetCallDumpAsanCovStmt();
    block->InsertBefore(crash_branch, crash_call);
    block->InsertBefore(crash_branch, dump_call);
    asanCovStmts.push_back(StmtBlockNodePair(crash_call, block));
    asanCovStmts.push_back(StmtBlockNodePair(dump_call, block));
  }
}

void ASanRazor::InstrumentIfStmtForProfiling(IfStmtNode *ifStmt) {
  CHECK_NULL_FATAL(ifStmt);
  if (!ASAN_RAZOR_INSTRUMENT_ASAN_CHECKS_ONLY 
      &&!this->udchain.UnmatchUserCheck(ifStmt->GetStmtID(), asanStmtIDSet)      
  ) {
    AsanRazorCheckID true_id = GetTrueBranchID(ifStmt);
    AsanRazorCheckID false_id = GetFalseBranchID(ifStmt);

    CallNode *true_call = GetCallAsanCovStmt(true_id);
    CallNode *false_call = GetCallAsanCovStmt(false_id);
    ifStmt->GetThenPart()->InsertFirst(true_call);
    ifStmt->GetElsePart()->InsertFirst(false_call);
    asanCovStmts.push_back(StmtBlockNodePair(true_call, ifStmt->GetThenPart()));
    asanCovStmts.push_back(StmtBlockNodePair(false_call, ifStmt->GetElsePart()));
  }
  InstrumentBlockForProfiling(ifStmt->GetThenPart());
  InstrumentBlockForProfiling(ifStmt->GetElsePart());
}

void ASanRazor::InstrumentWhileStmtForProfiling(WhileStmtNode *whileStmt, BlockNode *block) {
  CHECK_NULL_FATAL(whileStmt);
  if (!ASAN_RAZOR_INSTRUMENT_ASAN_CHECKS_ONLY
      &&!this->udchain.UnmatchUserCheck(whileStmt->GetStmtID(), asanStmtIDSet)
  ) {
    AsanRazorCheckID true_id = GetTrueBranchID(whileStmt);
    AsanRazorCheckID false_id = GetFalseBranchID(whileStmt);

    CallNode *true_call = GetCallAsanCovStmt(true_id);
    CallNode *false_call = GetCallAsanCovStmt(false_id);
    whileStmt->GetBody()->InsertFirst(true_call);
    block->InsertAfter(whileStmt, false_call);
    asanCovStmts.push_back(StmtBlockNodePair(true_call, whileStmt->GetBody()));
    asanCovStmts.push_back(StmtBlockNodePair(false_call, block));
  }
  InstrumentBlockForProfiling(whileStmt->GetBody());
}

void ASanRazor::InstrumentDoloopForProfiling(DoloopNode *doloop, BlockNode *block) {
  CHECK_NULL_FATAL(doloop);
  if (!ASAN_RAZOR_INSTRUMENT_ASAN_CHECKS_ONLY
      &&!this->udchain.UnmatchUserCheck(doloop->GetStmtID(), asanStmtIDSet)
  ) {
    AsanRazorCheckID true_id = GetTrueBranchID(doloop);
    AsanRazorCheckID false_id = GetFalseBranchID(doloop);

    CallNode *true_call = GetCallAsanCovStmt(true_id);
    CallNode *false_call = GetCallAsanCovStmt(false_id);
    doloop->GetDoBody()->InsertFirst(true_call);
    block->InsertAfter(doloop, false_call);
    asanCovStmts.push_back(StmtBlockNodePair(true_call, doloop->GetDoBody()));
    asanCovStmts.push_back(StmtBlockNodePair(false_call, block));
  }
  InstrumentBlockForProfiling(doloop->GetDoBody());
}

void ASanRazor::InstrumentForeachelemForProfiling(ForeachelemNode *foreachelem, BlockNode *block) {
  CHECK_NULL_FATAL(foreachelem);
  if (!ASAN_RAZOR_INSTRUMENT_ASAN_CHECKS_ONLY
    &&!this->udchain.UnmatchUserCheck(foreachelem->GetStmtID(), asanStmtIDSet)
  ) {
    AsanRazorCheckID true_id = GetTrueBranchID(foreachelem);
    AsanRazorCheckID false_id = GetFalseBranchID(foreachelem);

    CallNode *true_call = GetCallAsanCovStmt(true_id);
    CallNode *false_call = GetCallAsanCovStmt(false_id);
    foreachelem->GetLoopBody()->InsertFirst(true_call);
    block->InsertAfter(foreachelem, false_call);
    asanCovStmts.push_back(StmtBlockNodePair(true_call, foreachelem->GetLoopBody()));
    asanCovStmts.push_back(StmtBlockNodePair(false_call, block));
  }
  InstrumentBlockForProfiling(foreachelem->GetLoopBody());
}

void ASanRazor::InstrumentCondGotoForProfiling(CondGotoNode *condGoto, BlockNode *block, bool isTrue) {
  CHECK_NULL_FATAL(condGoto);
  if (ASAN_RAZOR_INSTRUMENT_ASAN_CHECKS_ONLY) return;
  if (this->udchain.UnmatchUserCheck(condGoto->GetStmtID(), asanStmtIDSet)) return;
  // the offset is used as LabelIdx
  LabelIdx labelIdx = condGoto->GetOffset();
  LabelNode *label = this->labelIdx2LabelNode[labelIdx];
  BlockNode* labelBlock = this->labelIdx2BlockNode[labelIdx];
  CHECK_NULL_FATAL(label);
  AsanRazorCheckID true_id = GetTrueBranchID(condGoto);
  AsanRazorCheckID false_id = GetFalseBranchID(condGoto);

  CallNode *true_call = GetCallAsanCovStmt(true_id);
  CallNode *false_call = GetCallAsanCovStmt(false_id);
  if (isTrue) {
    // if the condition is satisfied, we jump to the label
    // we add a GotoNode to jump to the instruction next the label
    // so that the inserted call will only be executed when
    // we jump to its label after the brtrue
    /*
    brtrue L1 (condition)
    false_call
    ...
    goto oldLabel
    L1: true_call
    OldLabel: ...
    */
    block->InsertAfter(condGoto, false_call);
    LabelIdx true_call_labelIdx = module->CurFunction()->GetLabelTab()->CreateLabel();
    LabelNode *true_call_label = module->GetMIRBuilder()->CreateStmtLabel(true_call_labelIdx);
    GotoNode* toOldLabel = module->GetMIRBuilder()->CreateStmtGoto(OP_goto, labelIdx);
    condGoto->SetOffset(true_call_labelIdx);
    labelBlock->InsertBefore(label, toOldLabel);
    labelBlock->InsertBefore(label, true_call_label);
    labelBlock->InsertBefore(label, true_call);
    asanCovStmts.push_back(StmtBlockNodePair(false_call, block));
    asanCovStmts.push_back(StmtBlockNodePair(toOldLabel, labelBlock));
    // NOTE: since we change the labelIdx of condGoto, the true_call_label is not removed
    // asanCovStmts.push_back(StmtBlockNodePair(true_call_label, labelBlock));
    asanCovStmts.push_back(StmtBlockNodePair(true_call, labelBlock));
  }
  else {
    block->InsertAfter(condGoto, true_call);
    LabelIdx false_call_labelIdx = module->CurFunction()->GetLabelTab()->CreateLabel();
    LabelNode *false_call_label = module->GetMIRBuilder()->CreateStmtLabel(false_call_labelIdx);
    GotoNode* toOldLabel = module->GetMIRBuilder()->CreateStmtGoto(OP_goto, labelIdx);
    condGoto->SetOffset(false_call_labelIdx);
    labelBlock->InsertBefore(label, toOldLabel);
    labelBlock->InsertBefore(label, false_call_label);
    labelBlock->InsertBefore(label, false_call);
    asanCovStmts.push_back(StmtBlockNodePair(true_call, block));
    asanCovStmts.push_back(StmtBlockNodePair(toOldLabel, labelBlock));
    // NOTE: since we change the labelIdx of condGoto, the false_call_label is not removed
    // asanCovStmts.push_back(StmtBlockNodePair(false_call_label, labelBlock));
    asanCovStmts.push_back(StmtBlockNodePair(false_call, labelBlock));
  }
}

bool ASanRazor::InstrumentBlockForProfiling(BlockNode *block) {
  bool changed = false;
  MapleVector<StmtNode*> origin_stmts(this->module->GetMPAllocator().Adapter());
  for (StmtNode &stmt : block->GetStmtNodes()) {
    origin_stmts.emplace_back(&stmt);
  }
  bool in_skip_asan = false;
  StmtNode *asan_last_stmt = nullptr;
  for (StmtNode* stmt : origin_stmts) {
    if (in_skip_asan) {
      if (stmt != asan_last_stmt)
        continue;
      else {
        // reach the end of asan check
        in_skip_asan = false;
        asan_last_stmt = nullptr;
        continue;
      }
    }
    if (asanStmtIDSet.find(stmt->GetStmtID()) != asanStmtIDSet.end()) {
      // this is an asan check stmt
      const AsanVirtualBlock *asanBlock = stmtID2asanBlock[stmt->GetStmtID()];
      InstrumentAsanCheckForProfiling(stmt, *asanBlock, block);
      in_skip_asan = true;
      asan_last_stmt = asanBlock->last;
      changed = true;
      SanCheckStmtID.insert(stmt->GetStmtID());
      continue;
    }
    switch (stmt->GetOpCode()) {
      case OP_block: {
        InstrumentBlockForProfiling(dynamic_cast<BlockNode *>(stmt));
        changed = true;
        break;
      }
      case OP_if: {
        IfStmtNode *ifStmt = dynamic_cast<IfStmtNode *>(stmt);
        InstrumentIfStmtForProfiling(ifStmt);
        changed = true;
        break;
      }
      case OP_while: {
        WhileStmtNode *whileStmt = dynamic_cast<WhileStmtNode *>(stmt);
        InstrumentWhileStmtForProfiling(whileStmt, block);
        changed = true;
        break;
      }
      case OP_doloop: {
        DoloopNode *doloop = dynamic_cast<DoloopNode *>(stmt);
        InstrumentDoloopForProfiling(doloop, block);
        changed = true;
        break;
      }
      case OP_foreachelem: {
        ForeachelemNode *foreachStmt = dynamic_cast<ForeachelemNode *>(stmt);
        InstrumentForeachelemForProfiling(foreachStmt, block);
        changed = true;
        break;
      }
      case OP_brtrue: {
        CondGotoNode *condGoto = dynamic_cast<CondGotoNode *>(stmt);
        InstrumentCondGotoForProfiling(condGoto, block, true);
        changed = true;
        break;
      }
      case OP_brfalse: {
        CondGotoNode *condGoto = dynamic_cast<CondGotoNode *>(stmt);
        InstrumentCondGotoForProfiling(condGoto, block, false);
        changed = true;
        break;
      }
      default: {
        break;
      }

      if (changed){
        UserCheckStmtID.insert(stmt->GetStmtID());
      }

    }
  }
  return changed;
}

std::set<StmtID> ASanRazor::GetRemoveAsanID() {
  // if sc sc matched, we kill the large ID one
  // By heurstics, this is later one
  // if uc sc matched, we kill the sc  
  std::set<StmtID> removeStmtID;
  std::map<StmtID, set_check*> found_SC;
  //  asanStmtIDSet
  for (auto &stmtid : SanCheckStmtID){
    if (this->udchain.dassignID2dep.find(stmtid) != this->udchain.dassignID2dep.end()){
      found_SC[stmtid] = &this->udchain.dassignID2dep[stmtid];
    }
  }
  float sc_counter = 0 ; 
  float sc_tot = found_SC.size();
  for (const auto& pair0 : found_SC){
    if (removeStmtID.count(pair0.first) != 0) continue;
    san_struct struct0 = Get_san_struct(pair0.first);
    for (const auto& pair1 : found_SC){
      if (removeStmtID.count(pair1.first) != 0) continue;
      if (pair0.first == pair1.first) continue;
      san_struct struct1 = Get_san_struct(pair1.first); 
      if (this->udchain.sat_check(*pair0.second, *pair1.second)){
        if (this->udchain.dynamic_sat(struct0, struct1, true)){
          removeStmtID.insert(pair0.first);
          sc_counter += 1;
        }
      }
    }
  }
  float uc_counter = 0;
  float uc_tot = UserCheckStmtID.size();
  for (const auto& UcStmtID : UserCheckStmtID) {
    set_check * ptr = this->udchain.GetSetCheck(UcStmtID);
    if (ptr == nullptr) continue;
    san_struct UcStruct = Get_san_struct(UcStmtID);
    for (const auto& pair : found_SC) {
      if (removeStmtID.count(pair.first) != 0) continue;
      if (pair.first == UcStmtID) continue;
      san_struct ScStruct = Get_san_struct(pair.first); 
      if (this->udchain.sat_check(*pair.second, *ptr)) {
        if (this->udchain.dynamic_sat(ScStruct, UcStruct, false)){
          removeStmtID.insert(pair.first);
          uc_counter += 1;
        }
      }
    }
  }
  if (ASAN_DEBUG_MODE){
    LogInfo::MapleLogger() << "ASAN-ASAN pair Matched: " << sc_counter << " / " << ((sc_tot*sc_tot) - sc_tot) /2.0 << "\n";
    LogInfo::MapleLogger() << "User Check ASAN Check Matched: " << uc_counter<< " / " << ((uc_tot*sc_tot)) << "\n";
    LogInfo::MapleLogger() << "Number of ASAN Checks can be removed: " << removeStmtID.size() << " / "<<found_SC.size()<< "\n";
  }
  return removeStmtID;
}

san_struct ASanRazor::Get_san_struct(StmtID id){
  san_struct ret;
  ret.stmtID = id;
  ret.true_ctr = 0;
  ret.false_ctr = 0;

  size_t TrueID = SanRazorCallSiteIdMap[AsanRazorCheckID((id << 1) | 0x1)];
  size_t FalseID = SanRazorCallSiteIdMap[AsanRazorCheckID(id << 1)];
  ASanRazorStmtKey T_key(TrueID, srcpath);
  ASanRazorStmtKey F_key(FalseID, srcpath);
  ret.true_ctr = SanRazorStmtCountMap[T_key];
  ret.false_ctr = SanRazorStmtCountMap[F_key];
  ret.tot_ctr = ret.true_ctr + ret.false_ctr;
  return ret;
}

}  // namespace maple

#endif  // ENABLE_MAPLE_SAN